#include<iostream>
using namespace std;
//一、this指针             -----也就是类的成员函数，不管是自己定义的函数默认的都会有一个默认的参数  对象类型的指针 例如;data* this，这个指针就是指向调用的对象
//隐含的this指针           隐函数的this指针的形式是这样的 例如：data* const this；
//class data
//{
//public:
//    void Init(int year,int month,int day)
//    {
//        //第一种方法，用不同的名字区别
////        _year=year;
////        _month=month;
////        _day=day;
//
//        //第二种方法,用解析域的方法
//        data::year=year;//但是尽量别这样
//        data::month=month;
//        data::day=day;
//    }
//    //这个函数被调用后会变成
////    // void Init(data* this,int year,int month,int day)
////    {
////        this->year=year;
////        this->month=month;
////        this->day=day;
////    }
//
//    void  Print()    //其实是有一个隐藏的指针，在对象调用这个函数后会变成void Print(data* this);
//    {
//        cout<<year<<' '<<month<<' '<<day<<endl;
//        //在对象调用这个语句时也会变成  cout<<this->year<<' '<<this->month<<' '<<this->day<<endl;  谁调用this指针就换成谁
//        //包括上面的那个函数，虽然不允许成员函数形参中 定义this指针，但是不定义也可以直接使用
//        cout<<this->year<<":测试成功"<<endl;//但是一般情况下我们都不会直接将this写出来。虽然可以
//    }
//
//private:
//    int year;
//    int month;
//    int day;
//};


//成员函数中规定，成员函数不能传实参给this，调用的时候，不能将对象的地址传过去         ----可以说成员函数哪怕不定义参数也会默认有一个，并且还不能显式的定义出来
//也就是不能这样定义函数 void test(data* this)  这个隐藏的this指针是当做关键字使用的，会报错（也就说不能将对象本身的参数传过去，无论以什么样的方式）

//int main(void)
//{
//    data d1;
//    data d2;//d1和d2的成员变量是不同的，但是调用的函数是相同的
//    const int* p1=nullptr;
//    int* p2=nullptr;
////    p1=p2;//不能通过*p1改变保存的值，但是能改变p1指向的地址
////    p2=p1;//这里报错是因为p1这个地址保存的变量是const的，p2是可读可写的，权限放大了
//
//    d1.Init(2019,06,01);//这个初始化和下面的输出是怎么调用的？
//    d2.Init(2019,07,01);//在调用这个函数的时候，会变成  d2.Init(&d2,2019,07,01);
//
//    d1.Print();//调用的输出名字都是一样的编译器怎么知道输出的是哪个对象的成员变量？
//    d2.Print();//调用的时候默认变成d2.Print(&d2),因为是结构体，所以上面cout输出的方式就是指针地址this->这种方式
//}

//this指针是存在栈区的，因为他是形参，所以是在栈上




//两道测试题
//class test
//{
//public:
//    void show1()
//    {
//        cout<<"嘿嘿嘿"<<endl;
//    }
//
//    void show2()
//    {
//        cout<<a<<endl;
//    }
//private:
//    int a;
//};
//int main(void)
//{
//    test* t1=nullptr;//t1是一个结构体的地址
//    t1->show1();//这个没错    虽然是空指针，但是编译阶段检查不出来，调用成员函数show1时，不会访问指针，所以正常运行
//
//    t1->show2();//这个是错误的      访问空指针了，会将空指针传给this，然后this->a出现错误
//}












//二、类的默认成员函数                      ----还是拿data类作为例子
//1.如果一个类中什么成员都没有，简称为空类。空类中如果我们什么成员都不设置，就会默认生成6个成员函数         前四个函数比较重要




//2.初始化和清理:        ----构造函数和析构函数

//构造函数：主要完成初始化工作    类似Init                 在对象的定义阶段并完成初始化就会使用构造     data d4=d2;
//构造函数解释：构造函数是特殊的成员函数，需要注意的是，构造函数虽然名称叫做构造，但是构造函数的主要功能是初始化对象。这个函数并不会开空间创建对象
//构造函数特征：函数名和类名相同   没有返回值    对象实例化编译器自动调用对应的构造函数        构造函数可以重载

//如果类中没有显示的定义构造函数，c++编译器会自动生成一个无参的默认构造函数，一旦用户显式定义，编译器降不造生成
//但是不能在主函数中显示的调用构造函数，也就是不能将这个函数出现在主函数的代码页面
//class test
//{
//public:
//    test()//如果类test的默认构造函数是有参数的就会报错test(int a),但是全缺省参数可以test(int a=9)
//    {
//        cout<<"test()"<<endl;
//        _a=10086;
//    }
//private:
//    int _a;
//};
//
//class data
//{
//public:
//    data(int year=0,int month=1,int day=2)//默认构造函数，推荐使用全缺省，咱们这样是显式的写了出来          构造函数就是和类名相同的函数      函数前面没有返回类型
//    //把这个函数注释掉，系统会生成一个无参的构造函数，但是这个系统给的构造函数，会根据参数的类型进行不同的处理
//    //类型分为两种：内置类型（int,char,double,指针,数组等）、自定义类型（struct、class定义的类型）
//    //默认生成的无参构造函数对内置类型不进行处理        自定义类型才会去调用无参默认构造函数去进行初始化（成员变量中有个类的变量）
//    //但是这个自定义类型变量里面还是会有构造函数
//    {
//        _year=year;
//        _month=month;
//        _day=day;
//    }
//
//    void Print()
//    {
//        cout<<_year<<' '<<_month<<' '<<_day<<endl;
//    }
//private:
//    int _year;
//    int _month;
//    int _day;
//    test t1;
//};
//int main(void)
//{
////    data d1();//这样定义可以通过，因为编译器可能当做函数的命名来处理
////    d1.Print();//所以虽然d1通过了，但是不能调用成员函数
//
//    data d3;//想要调用构造含函数只能这样使用，不能加括号
//    d3.Print();
//
//    data d2(2019,6,1);//可以直接这样进行初始化（有缺省参数的方式）      这样使用的原因就是构造函数不能显示的出现在代码中，想传参就这样
//    d2.Print();
//}

//总结：无参的构造函数和全缺省的构造函数都称为默认构造函数，并且默认构造函数只有一个这两个是自己写的。这两个不写，编译器也会自动生成默认的无参构造函数







//析构函数：主要完成清理工作      类似Destory
//析构函数概念：与构造功能相反，析构函数并不是完成对象的销毁，局部对象销毁工作是由编译器完成的。对象在销毁时才会自动调用析构函数，完成对象的一些资源清理工作

//析构函数特征：                  -----析构函数是特殊的成员函数
//析构函数的函数名是在类名前面加上波浪线   ~test()
//同样是无参数无返回值
//一个类有且只有一个构造函数。没显式的定义，就会自动生成默认的析构函数
//对象生命周期结束时，c++编译系统自动调用析构函数
//class data
//{
//public://哪怕什么函数都不写也要带上public，只有带上这个能调用系统自动生成的6个成员函数
//    ~data()//你自己不去定义free（p），默认的析构函数会将堆上的空间，在对象的生命周期结束后自动释放空间
//    {
//        cout<<"hehe"<<endl;//测试是否自动调用析构函数
////        free(p)
//    }
//
//private:
//    int _year;
//    int _month;
//    int _day;
////    int* p=(int*)malloc(sizeof(int));//如过我有成员是在堆上的
//};
//int main()
//{
//    data d1;
//    data d2;
//    data d3;//但是析构函数是d3先调用，然后d2调用，最后才d1
//    return 0;//但是如果类中的成员变量和在此处定义的变量都在栈中，析构函数定义出来不做任何处理都可以
//}
//并且析构函数和构造函数相同，不处理内置类型成员变量，自定义类型才会调用析构函数！！！！！！！                 可做两个栈实现一个队列oj题



//析构函数  优先倒着析构局部变量的类，全局变量个static定义的属性是相同的，这两个也是按顺序，但是这两个析构的比局部变量晚
//data d;
//int main(void)
//{
//    data a;
//    data b;
//    static data c;
//}
//函数结束后析构的顺序是 b a c d     也就是说先定义的先析构









//3.拷贝复制                         -----拷贝构造函数
//拷贝构造函数概念：只用单个形参，该形参是本类 类型对象的引用（一般是用const修饰），在用已存在类类型对象创建新对象时由编译器自动调用           引用是起别名！！！
//要用这个函数，这个类已经定义过其他对象了

//拷贝构造函数特征：
//拷贝构造函数是构造函数的一个重载的形式                拷贝构造函数个构造函数的名字是一样的
//拷贝构造函数的参数只用一个，且必须使用引用传参，使用传值方式会引发无穷递归调用

//
//class data {
//private:
//    int _year;
//    int _month;
//    int _day;
////    int* p=(int*)malloc(sizeof(4));
//
//public:
//    data(const data &dd)        //如果说这个参数不是引用，那么就会无限递归下去 也就是这样定义函数  data(data dd)
//    {//无限递归的原因，实参传给形参，本身就是一个拷贝的过程。又因为属于类的拷贝，又会触发形参传实参这个过程。无限递归下去   d1->dd需要拷贝->dd需要拷贝....
//        //建议加上const 避免一些错误的操作
//        _year = dd._year;
//        _month=dd._month;
//        _day=dd._day;
//    }
//
//
////默认拷贝构造函数：                  //按字节拷贝  就是浅拷贝
////如果自己没有定义拷贝构造函数，那么自动生成拷贝构造函数的会将传入得引用按照字节赋值给新对象。     但是请注意有一点
////成员变量中有在堆上定义的空间，那么新的对象会将老对象的指针赋值过去，新对象成员指针也是指向老对象的地址的。
////然后先析构新对象的指针（free()释放掉），因为释放后习惯性置空了，然后再次析构的时候就会出现释放空指针的情况，就会出现错误
////一块空间不能释放两次
//
//
//
//    data(int year = 0, int month = 1, int day = 2)//默认构造函数，推荐使用全缺省，咱们这样是显式的写了出来          构造函数就是和类名相同的函数      函数前面没有返回类型
//    {
//        _year = year;
//        _month = month;
//        _day = day;
//    }
//};
//
//
//int main()
//{
//    data d1;
//    data d2(d1);
//    return 0;
//}
//拷贝构造函数默认生成的，对于内置类型和自定义类型都会处理
//内置类型会拷贝 老对象的内置类型成员变量
//自定义类型会使用自己的拷贝构造函数

//拷贝构造函数只要涉及到一个类变量的初始化就会自动调用









